﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;
using Amazon.SimpleDB;
using Amazon.SimpleDB.Model;

namespace SimpleDbBrowser.ViewModel
{
    /// <summary>
    /// The view model describing the UI for the CustomSelect task.
    /// </summary>
    public sealed class CustomSelectViewModel : ViewModelBase
    {
        private readonly AmazonSimpleDB db;
        private int cnt;
        private string dmnName = string.Empty;
        private string err;
        private string qry = string.Empty;

        private readonly ObservableCollection<Tuple<string, string, string>> qryResults =
            new ObservableCollection<Tuple<string, string, string>>();

        /// <summary>
        /// Initializes a new instance of the CustomSelectViewModel class.
        /// </summary>
        /// <param name="database">
        /// The simpleDB that the SELECT query will be performed against.
        /// </param>
        public CustomSelectViewModel(AmazonSimpleDB database)
        {
            if (database == null) throw new ArgumentNullException("database");

            this.db = database;

            this.SubmitCustomQuery = new RelayCommand(this.OnSubmitCustomQuery, this.CanSubmitCustomQuery);
        }

        #region Commands

        /// <summary>
        /// Gets the command that will submit a custom query.
        /// </summary>
        public ICommand SubmitCustomQuery { get; private set; }

        /// <summary>
        /// Performs the SubmitCustomQuery command.
        /// </summary>
        /// <param name="param">
        /// This is not used.
        /// </param>
        private void OnSubmitCustomQuery(object param)
        {
            var customQuery = this.qry;

            var worker = new BackgroundWorker();

            worker.DoWork += (s, args) =>
                                 {
                                     var req = new SelectRequest().WithSelectExpression(customQuery);
                                     var resp = this.db.Select(req);
                                     args.Result = resp.SelectResult;
                                 };

            worker.RunWorkerCompleted += this.OnSubmitCustomQueryWorkerCompleted;

            worker.RunWorkerAsync();

            return;
        }

        /// <summary>
        /// Handles logic that must be performed once the custom query has 
        /// been submitted and a response has been received.
        /// </summary>
        /// <param name="s">
        /// The DoWork parameter.  This is not used.
        /// </param>
        /// <param name="args">
        /// The results of the custom query.
        /// </param>
        private void OnSubmitCustomQueryWorkerCompleted(object s, RunWorkerCompletedEventArgs args)
        {
            this.qryResults.Clear();

            if (args.Error != null)
            {
                this.ErrorMessage = args.Error.Message;
                return;
            }

            this.ErrorMessage = null;

            var res = args.Result as SelectResult;
            Debug.Assert(res != null);

            var flattened = res.Item.SelectMany(i => i.Attribute, (i, a) => Tuple.Create(i.Name, a.Name, a.Value));

            foreach (var tuple in flattened)
            {
                this.qryResults.Add(tuple);
            }

            this.ResultCount = this.qryResults.Count;
        }

        /// <summary>
        /// Determines whether a custom query can currently be submitted.
        /// </summary>
        /// <param name="param">
        /// This is not used.
        /// </param>
        /// <returns>
        /// True if the custom query can be submitted, otherwise false.
        /// </returns>
        private bool CanSubmitCustomQuery(object param)
        {
            return !string.IsNullOrWhiteSpace(this.qry);
        }

        #endregion

        #region Bindable Properties

        /// <summary>
        /// Gets the display name of this view model.
        /// </summary>
        public override string DisplayName
        {
            get { return "Perform a custom select query"; }
            protected set { throw new NotSupportedException(); }
        }

        /// <summary>
        /// Gets or sets the name of the domain that is currently selected.
        /// </summary>
        public string DomainName
        {
            get { return this.dmnName; }
            set
            {
                if (this.dmnName == value) return;

                this.dmnName = value;
                this.OnDomainNameChanged();

                Debug.WriteLine(string.Format("Set CustomSelectViewModel.DomainName to {0}", this.dmnName));
            }
        }

        /// <summary>
        /// Gets or sets the query that will be submitted when the 
        /// SubmitCustomQuery command is executed.
        /// </summary>
        public string Query
        {
            get { return this.qry; }
            set
            {
                if (this.qry == value) return;

                this.qry = value;
                this.OnQueryChanged();

                Debug.WriteLine(string.Format("Set CustomSelectViewModel.Query to {0}", this.qry));
            }
        }

        /// <summary>
        /// Gets or sets the error message that the query resulted in, if an error occurred.
        /// </summary>
        public string ErrorMessage
        {
            get { return this.err; }
            set
            {
                if (this.err == value) return;

                this.err = value;
                this.OnPropertyChanged("ErrorMessage");

                Debug.WriteLine(string.Format("set CustomSelectviewModel.ErrorMessage to {0}", this.err));
            }
        }

        /// <summary>
        /// Gets a collection of the query results if the query was successful.
        /// </summary>
        /// <remarks>
        /// The first string is the item name, the second string is the 
        /// attribute name, the third string is the attribute value.
        /// </remarks>
        public ReadOnlyObservableCollection<Tuple<string, string, string>> QueryResults
        {
            get { return new ReadOnlyObservableCollection<Tuple<string, string, string>>(this.qryResults); }
        }

        /// <summary>
        /// Gets or sets a count of the results returned.
        /// </summary>
        public int ResultCount
        {
            get { return this.cnt; }
            set
            {
                if (this.cnt == value) return;

                this.cnt = value;
                this.OnPropertyChanged("ResultCount");

                Debug.WriteLine(string.Format("Set CustomSelectViewModel.ResultCount to {0}", this.cnt));
            }
        }

        #endregion

        /// <summary>
        /// Performs actions that must take place when the DomainName property
        /// is changed.
        /// </summary>
        private void OnDomainNameChanged()
        {
            this.OnPropertyChanged("DomainName");
        }

        /// <summary>
        /// Performs actions that must take place when the Query property is
        /// changed.
        /// </summary>
        private void OnQueryChanged()
        {
            this.OnPropertyChanged("Query");
        }
    }
}